package com.haoran.dwz.common.security;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.haoran.dwz.domain.entities.Permission;
import com.haoran.dwz.domain.entities.Role;
import com.haoran.dwz.repository.PermissionRepository;

/**
 * 最核心的地方，就是提供某个资源对应的权限定义，即getAttributes方法返回的结果。 此类在初始化时，应该取到所有资源及其对应角色的定义。
 * 
 * 这里涉及到分布式缓存
 */
@Service
public class UsmFilterInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {

	public static final String PERMISSION_KEY = "PERMISSION_KEY";

	private Logger logger = LoggerFactory.getLogger(UsmFilterInvocationSecurityMetadataSourceService.class);

	@Autowired
	private PermissionRepository permissionRepository;

	@Autowired
	private ApiPathPattern apiPathPattern;

	/**
	 * 本实例化调用 查询所有的权限项 然后同步到缓存
	 */
	@PostConstruct
	public void synchronizeSecurityMetadataSourcetToCache() {
		MetadataSourceCacheManager.metadata_source_map.clear();
		List<Permission> list = permissionRepository.findAll();
		MetadataSourceCacheManager.put(list);
	}

	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
		for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : MetadataSourceCacheManager.metadata_source_map
				.entrySet()) {
			allAttributes.addAll(entry.getValue());
		}
		return allAttributes;
	}

	/**
	 * 根据URL，找到相关的权限配置 如果是静态文件就返回null
	 */
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
		// object 是一个URL，被用户请求的url
		FilterInvocation filterInvocation = (FilterInvocation) object;
		HttpServletRequest httpRequest = filterInvocation.getHttpRequest();
		if(apiPathPattern.getPublicRequestMatcher().matches(httpRequest)) {
			logger.debug(httpRequest.getPathInfo());
			return null;
		}
		if (apiPathPattern.getStaticsRequestMatcher().matches(httpRequest)) {
			logger.debug(httpRequest.getPathInfo());
			return null;
		}
		Iterator<RequestMatcher> ite = MetadataSourceCacheManager.metadata_source_map.keySet().iterator();
		while (ite.hasNext()) {
			RequestMatcher requestMatcher = ite.next();
			if (requestMatcher.matches(httpRequest)) {
				return MetadataSourceCacheManager.metadata_source_map.get(requestMatcher);
			}
		}
		return null;
	}

	/**
	 * 缓存管理器 所有都是私有的只有通过 {@link UsmFilterInvocationSecurityMetadataSourceService} 的
	 * remove 或者 refresh 方法 更新缓存
	 *
	 */
	private static class MetadataSourceCacheManager {

		private static Logger logger = LoggerFactory.getLogger(MetadataSourceCacheManager.class);

		private static final Map<RequestMatcher, Collection<ConfigAttribute>> metadata_source_map = new ConcurrentHashMap<RequestMatcher, Collection<ConfigAttribute>>();

		/**
		 * 更新缓存
		 * 
		 * @param permission
		 * @return
		 */
		private static Map<RequestMatcher, Collection<ConfigAttribute>> put(Collection<Permission> permissions) {
			for (Permission permission : permissions) {
				recursionPermissions(permission);
			}
			return metadata_source_map;
		}

		private static void recursionPermissions(Permission... permissions) {
			recursionPermissions(Arrays.asList(permissions));
		}

		private static void recursionPermissions(List<Permission> permissions) {
			for (Permission permission : permissions) {
				UsmRequestMatcher permissionMather = UsmRequestMatcher.Builder.buildWithPermission(permission);
				if (permissionMather.getRequestMatchers().isEmpty()) {
					logger.info("Remove cache UsmRequestMatcher[ permissionId=" + permissionMather.getPermissionId()
							+ "] becase of it`s URL is null or empty");
					metadata_source_map.remove(permissionMather);
				} else {
					Collection<ConfigAttribute> roleCollection = new HashSet<ConfigAttribute>();
					StringBuilder sb = new StringBuilder("Authority: ");
					Set<Role> roles = permission.getRoles();
					for (Role role : roles) {
						ConfigAttribute roleNumber = new SecurityConfig(role.getAuthority());
						roleCollection.add(roleNumber);
						sb.append("[").append(role.getAuthority()).append("] ");
					}
					logger.info("Put cache UsmRequestMatcher { permissionId=" + permissionMather.getPermissionId()
							+ sb.toString() + permissionMather.getPatterns() + " } ");
					metadata_source_map.put(permissionMather, roleCollection);//
				}
				recursionPermissions(permission.getChildren());
			}
		}
		
//		private static Map<RequestMatcher, Collection<ConfigAttribute>> put(Permission permission) {
//			recursionPermissions(permission);
//			return metadata_source_map;
//		}
//		
//		private static Map<RequestMatcher, Collection<ConfigAttribute>> remove(Collection<Permission> permissions) {
//			for (Permission permission : permissions) {
//				remove(permission);
//			}
//			return metadata_source_map;
//		}
//
//		/**
//		 * 移除缓存
//		 * 
//		 * @param permission
//		 * @return
//		 */
//		private static Map<RequestMatcher, Collection<ConfigAttribute>> remove(Permission permission) {
//			UsmRequestMatcher permissionMatcher = UsmRequestMatcher.Builder.buildWithPermission(permission);
//			if (metadata_source_map.containsKey(permissionMatcher)) {
//				logger.info("Remove UsmRequestMatcher[ permissionId=" + permissionMatcher.getPermissionId()
//						+ "] becase the permissions involved are deleted");
//				metadata_source_map.remove(permissionMatcher);
//			}
//			return metadata_source_map;
//		}
	}

	private static final class UsmRequestMatcher implements RequestMatcher {

		private final Log logger = LogFactory.getLog(getClass());
		private final String permissionId;
		private final List<AntPathRequestMatcher> requestMatchers;

		public String getPermissionId() {
			return permissionId;
		}

		public List<AntPathRequestMatcher> getRequestMatchers() {
			return requestMatchers;
		}

		public String getPatterns() {
			StringBuilder sb = new StringBuilder("patterns: ");
			for (AntPathRequestMatcher requestMatcher : requestMatchers) {
				sb.append("[").append(requestMatcher.getPattern()).append("] ");
			}
			return sb.toString();
		}

		private UsmRequestMatcher(String permissionId) {
			this.permissionId = permissionId;
			this.requestMatchers = java.util.Collections.emptyList();
		}

		/**
		 * Creates a new instance
		 *
		 * @param requestMatchers
		 *            the {@link RequestMatcher} instances to try
		 */
		private UsmRequestMatcher(String permissionId, List<AntPathRequestMatcher> requestMatchers) {
			Assert.notEmpty(requestMatchers, "requestMatchers must contain a value");
			if (requestMatchers.contains(null)) {
				throw new IllegalArgumentException("requestMatchers cannot contain null values");
			}
			this.requestMatchers = requestMatchers;
			this.permissionId = permissionId;
		}

		/**
		 * Creates a new instance
		 *
		 * @param requestMatchers
		 *            the {@link RequestMatcher} instances to try
		 */
		private UsmRequestMatcher(String permissionId, AntPathRequestMatcher... requestMatchers) {
			this(permissionId, Arrays.asList(requestMatchers));
		}

		public boolean matches(HttpServletRequest request) {
			for (RequestMatcher matcher : requestMatchers) {
				if (logger.isDebugEnabled()) {
					logger.debug("Trying to match using " + matcher);
				}
				if (matcher.matches(request)) {
					logger.debug("matched");
					return true;
				}
			}
			logger.debug("No matches found");
			return false;
		}

		public static class Builder {
			public static UsmRequestMatcher buildWithPermission(Permission permission) {
				Assert.notNull(permission, "permission should not null!");
				if (StringUtils.isNotBlank(permission.getUrl())) {
					String[] urls = permission.getUrl().split(",");
					String httpMethod = permission.getHttpMethod();
					List<AntPathRequestMatcher> list = new ArrayList<>();
					for (int i = 0; i < urls.length; i++) {
						AntPathRequestMatcher pathRequestMatcher = new AntPathRequestMatcher(urls[i], httpMethod);
						list.add(pathRequestMatcher);
					}
					return new UsmRequestMatcher(permission.getId(), list);
				} else {
					return new UsmRequestMatcher(permission.getId());
				}
			}
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((permissionId == null) ? 0 : permissionId.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			UsmRequestMatcher other = (UsmRequestMatcher) obj;
			if (permissionId == null) {
				if (other.permissionId != null)
					return false;
			} else if (!permissionId.equals(other.permissionId))
				return false;
			return true;
		}
	}

	@Override
	public boolean supports(Class<?> arg0) {
		return true;
	}

}